﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Threading;
using System.Security.Principal;
/// PowerShell
using System.Management.Automation;
using System.Management.Automation.Runspaces;
/// Workflow
using System.Workflow.ComponentModel;
/// FIM
using Microsoft.ResourceManagement.Workflow.Activities;
using Microsoft.ResourceManagement.Workflow;
using System.Text;
using System.Collections;

namespace FimExtensions.FimActivityLibrary
{
    public partial class PowerShellActivity : Activity
    {
        /// TraceSource for the Activity
        private static TraceSourceEx trace = TraceSourceEx.Instance;

        #region Dependency Properties
        /// <summary>
        /// Dependency Properties for this activity
        /// </summary>
        public String Script
        {
            get { return (String)GetValue(ScriptProperty); }
            set { SetValue(ScriptProperty, value); }
        }
        public static readonly DependencyProperty ScriptProperty = DependencyProperty.Register("Script", typeof(String), typeof(PowerShellActivity));


        public static readonly DependencyProperty ImpersonateRequestorProperty = DependencyProperty.Register("ImpersonateRequestor", typeof(bool), typeof(PowerShellActivity));
        /// <summary>
        /// If this is true, the powershell call will be made in the context of the requestor
        /// </summary>
        public bool ImpersonateRequestor
        {
            // IMPORTANT: To maintain parity between setting a property in XAML and procedural code, do not touch the getter and setter inside this dependency property!
            get
            {
                return (bool)GetValue(ImpersonateRequestorProperty);
            }
            set
            {
                SetValue(ImpersonateRequestorProperty, value);
            }
        }
        
        #endregion

        /// <summary>
        /// PowerShellActivity Constructor
        /// </summary>
        public PowerShellActivity()
        {
            trace.TraceStart("PowerShellActivity Constructor");            
            InitializeComponent();
            trace.TraceStop("PowerShellActivity Constructor");
        }
        
        /// <summary>
        /// Run the script 
        /// </summary>
        /// <param name="ActivityExecutionStatus"></param>
        /// <returns></returns>
        protected override ActivityExecutionStatus Execute(ActivityExecutionContext context)
        {
            trace.TraceStart("START PowerShellActivity Execute");
            try
            {          
                    this.RunScript();
            }
            catch (Exception ex)
            {              
                throw new InvalidOperationException(ex.Message);
            }

            trace.TraceStop("STOP PowerShellActivity Execute");
            return ActivityExecutionStatus.Closed;
        }

        /// <summary>
        /// Runs the PowerShell Script using:
        ///  - FIM WorkflowData items as PowerShell variables
        ///  - the specified PowerShell module (if specified in the WF)
        ///  - stores the script output in a new FIM WorkflowData item (if specified in the WF)
        /// </summary>
        private void RunScript()
        {
            trace.TraceStart("START PowerShellActivity RunScript");
            string scriptToTrace = Script;

            if (scriptToTrace.Length > 32766)
                scriptToTrace = scriptToTrace.Substring(0, 32763) + "...";

            trace.TraceInfo("Executing: " + scriptToTrace);

            // In order to add our PowerShell result to the FIM Workflow Dictionary we need to get the containing (parent) workflow
            SequentialWorkflow containingWorkflow = null;
            if (!SequentialWorkflow.TryGetContainingWorkflow(this, out containingWorkflow))
            {
                trace.TraceError("Unable to get Containing Workflow.");
                throw new InvalidOperationException("Unable to get Containing Workflow");
            }

            ///
            /// Creating a PSobject to represent the Request Details            
            /// 
            PSObject psObject = new PSObject();
            psObject.Properties.Add(new PSNoteProperty("WorkflowDictionary", containingWorkflow.WorkflowDictionary));
            psObject.Properties.Add(new PSNoteProperty("RequestId", containingWorkflow.RequestId));
            psObject.Properties.Add(new PSNoteProperty("TargetId", containingWorkflow.TargetId));
            psObject.Properties.Add(new PSNoteProperty("ActorId", containingWorkflow.ActorId));
            psObject.Properties.Add(new PSNoteProperty("WorkflowDefinitionId", containingWorkflow.WorkflowDefinitionId));

            ///
            /// Put the Request Details into to PowerShell variables as a PSObject
            /// 
            InitialSessionState initialSessionState = InitialSessionState.CreateDefault();
            initialSessionState.Variables.Add(new SessionStateVariableEntry("FimWF", psObject, null));

            WindowsImpersonationContext impersonationContext = null;
            try
            {

                if (ImpersonateRequestor)
                {
                    trace.TraceVerbose("ImpersonateRequestor = true");
                    trace.TraceInfo("Attempting to impersonate requestor.");

                    try
                    {
                        impersonationContext = ((WindowsIdentity)Thread.CurrentPrincipal.Identity).Impersonate();
                    }
                    catch (Exception ex)
                    {
                        trace.TraceError("Error while attempting to impersonate: " + ex);
                        throw;
                    }
                    trace.TraceInfo("Impersonate request succesful.");
                }

                //trace.TraceInfo(String.Format("Executing workflow as {0}", Thread.CurrentPrincipal.Identity.Name));

                // Call the RunspaceFactory.CreateRunspace(InitialSessionState) 
                // method to create the runspace where the pipeline is run.
                Runspace runspace = RunspaceFactory.CreateRunspace(initialSessionState);
                runspace.Open();

                using (PowerShell powershell = PowerShell.Create())
                {
                    powershell.Runspace = runspace;
                    powershell.AddScript(Script);
                    powershell.AddParameter("Verbose");

                    // Invoke the PowerShell Pipeline synchronously
                    try
                    {
                        Collection<PSObject> results = powershell.Invoke();

                        Hashtable fimWFWorkflowDictionary = powershell.Runspace.SessionStateProxy.PSVariable.GetValue("fimWFWorkflowDictionary") as Hashtable;
                        if (fimWFWorkflowDictionary != null)
                        {
                            trace.TraceVerbose("Storing the PowerShell output in FIM WorkflowData.");
                            foreach (DictionaryEntry dictionaryEntry in fimWFWorkflowDictionary)
                            {
                                trace.TraceVerbose("Result Property:  {0}:{1}", dictionaryEntry.Key.ToString(), dictionaryEntry.Value);
                                containingWorkflow.WorkflowDictionary.Add(dictionaryEntry.Key.ToString(), dictionaryEntry.Value);
                            }
                        }

                        if (powershell.Streams.Error != null && powershell.Streams.Error.Count > 0)
                        {
                            // Throw on any errors
                            StringBuilder errorBuilder = new StringBuilder();
                            errorBuilder.AppendLine("PowerShell Activity");
                            foreach (ErrorRecord error in powershell.Streams.Error)
                            {
                                errorBuilder.AppendLine(error.ToString());
                            }
                            trace.TraceError(errorBuilder.ToString());

                            throw new WorkflowExtensionException(errorBuilder.ToString());
                        }
                    }
                    catch (RuntimeException ex)
                    {
                        trace.TraceError("PowerShell Error: {0}", ex.Message);
                        throw;
                    }
                    finally
                    {
                        //v-brdes 24Jul12 - moved this code here to do tracing when we encounter
                        // an exception in the script at powershell.Invoke();

                        // Output the Streams to trace
                        String verboseOutput = String.Format("PowerShell output from script: {0}\n", Script);
                        foreach (VerboseRecord verboseRecord in powershell.Streams.Verbose)
                        {
                            verboseOutput += String.Format("VERBOSE: {0}\n", verboseRecord);
                        }
                        foreach (ErrorRecord errorRecord in powershell.Streams.Error)
                        {
                            verboseOutput += String.Format("ERROR: {0}\n", errorRecord);
                        }
                        foreach (WarningRecord warningRecord in powershell.Streams.Warning)
                        {
                            verboseOutput += String.Format("WARNING: {0}\n", warningRecord);
                        }

                        if (verboseOutput.Length > 32766)
                        {
                            verboseOutput = verboseOutput.Substring(0, 32763) + "...";
                        }

                        trace.TraceVerbose(verboseOutput);
                    }
                }
            }
            finally
            {
                if (ImpersonateRequestor && impersonationContext != null)
                {
                    trace.TraceVerbose("Unimpersonating thread.");
                    impersonationContext.Undo();
                    impersonationContext.Dispose();
                }
            }

            trace.TraceStop("STOP PowerShellActivity RunScript");
        }        
    }
}
